﻿using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using Should;

namespace MugenMvvmToolkitTest.Utils
{
    public delegate void RefDelegate(ref object obj, out int i);

    public delegate int FuncValueDelegateRef(ref int obj);

    public class WeakEventClass
    {
        public const int Value = 10000;
        public static readonly object StaticObject = new object();

        public void RefDelegate(ref object obj, out int i)
        {
            obj = StaticObject;
            i = Value;
        }

        public object FuncDelegate(object obj1)
        {
            return StaticObject;
        }

        public static object StaticFuncDelegate(object obj)
        {
            return StaticObject;
        }

        public int FuncValueDelegate(int value)
        {
            return value;
        }

        public int FuncValueDelegateRef(ref int value)
        {
            value = Value;
            return value;
        }
    }

    public class WeakEventClassTarget
    {
        public event RefDelegate RefDelegate;

        public void OnRefDelegate(ref object obj, out int i)
        {
            i = 0;
            RefDelegate handler = RefDelegate;
            if (handler != null)
                handler(ref obj, out i);
        }

        public event Func<object, object> EventFunc;

        public object OnEventFunc(object arg)
        {
            Func<object, object> handler = EventFunc;
            if (handler != null)
                return handler(arg);            
            return null;
        }

        public event Func<int, int> FuncValueDelegate;

        public int OnFuncValueDelegate(int arg)
        {
            Func<int, int> handler = FuncValueDelegate;
            if (handler != null)
                return handler(arg);
            return default(int);
        }

        public event FuncValueDelegateRef FuncValueDelegateRef;

        public int OnFuncValueDelegateRef(ref int obj)
        {
            FuncValueDelegateRef handler = FuncValueDelegateRef;
            if (handler != null)
                return handler(ref obj);
            return default(int);
        }

        public Delegate[] GetRefDelagates()
        {
            if (RefDelegate == null)
                return null;
            return RefDelegate.GetInvocationList();
        }

        public Delegate[] GetEventFuncDelegates()
        {
            if (EventFunc == null)
                return null;
            return EventFunc.GetInvocationList();
        }

        public Delegate[] GetFuncValueDelegateRef()
        {
            if (FuncValueDelegateRef == null)
                return null;
            return FuncValueDelegateRef.GetInvocationList();
        }

        public Delegate[] GetFuncValueDelegate()
        {
            if (FuncValueDelegate == null)
                return null;
            return FuncValueDelegate.GetInvocationList();
        }
    }

    [TestClass]
    public class MvvmUtilsTest
    {
        [TestMethod]
        public void DefaultSettingValueTest()
        {
            GlobalSettings.SetDefaultSettings();
            GlobalSettings.FilterableCollectionExecutionMode.ShouldEqual(ExecutionType.AsynchronousInUi);
            GlobalSettings.SyncronizedCollectionExecutionMode.ShouldEqual(ExecutionType.AsynchronousInUi);

            GlobalSettings.DeadLockDetectionEnabled.ShouldBeTrue();
            GlobalSettings.DeadLockWaitTime.ShouldEqual(TimeSpan.FromSeconds(20));

            GlobalSettings.IsDebugMode.ShouldBeFalse();
            GlobalSettings.ListenViewModelMode.ShouldEqual(ListenType.Child);
            GlobalSettings.PropertyChangeExecutionMode.ShouldEqual(ExecutionType.AsynchronousInUi);
            GlobalSettings.CommandCanExecuteExecutionMode.ShouldEqual(ExecutionType.AsynchronousInUi);
            GlobalSettings.CommandExecutionMode.ShouldEqual(CommandExecutionType.CanExecuteBeforeExecute);
            GlobalSettings.UseWeakDelegate.ShouldBeFalse();
#if WPF
            GlobalSettings.UseCommandManager.ShouldBeTrue();
#endif

        }

        [TestMethod]
        public void WeakEventWithoutUnsubcribeTest()
        {
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc += MvvmUtils.ConvertToWeakDelegate(new Func<object, object>(target.FuncDelegate));
            eventClass.RefDelegate += MvvmUtils.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate));
            eventClass.FuncValueDelegate += MvvmUtils.ConvertToWeakDelegate(new Func<int, int>(target.FuncValueDelegate));
            eventClass.FuncValueDelegateRef +=
                MvvmUtils.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef));

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            int value = 999;
            int i;
            object obj = null;
            Assert.AreEqual(eventClass.OnEventFunc(new object()), WeakEventClass.StaticObject);
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), value);

            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), WeakEventClass.Value);
            Assert.AreEqual(value, WeakEventClass.Value);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.AreEqual(obj, WeakEventClass.StaticObject);
            Assert.AreEqual(i, WeakEventClass.Value);

            //clear target
            target.ToString();
            target = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            obj = null;
            Assert.IsNull(eventClass.OnEventFunc(new object()));
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), default(int));

            int value1 = value;
            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), default(int));
            Assert.AreEqual(value, value1);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.IsNull(obj);
            Assert.AreEqual(i, 0);

            Assert.AreEqual(eventClass.GetEventFuncDelegates().Length, 1);
            Assert.AreEqual(eventClass.GetRefDelagates().Length, 1);
            Assert.AreEqual(eventClass.GetFuncValueDelegate().Length, 1);
            Assert.AreEqual(eventClass.GetFuncValueDelegateRef().Length, 1);
        }

        [TestMethod]
        public void WeakEventWithUnsubcribeTest()
        {
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc +=
                MvvmUtils.ConvertToWeakDelegate(new Func<object, object>(target.FuncDelegate),
                                                func => eventClass.EventFunc -= func);
            eventClass.RefDelegate += MvvmUtils.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate),
                                                                      @delegate =>
                                                                      eventClass.RefDelegate -= @delegate);
            eventClass.FuncValueDelegate +=
                MvvmUtils.ConvertToWeakDelegate(new Func<int, int>(target.FuncValueDelegate),
                                                func => eventClass.FuncValueDelegate -= func);
            eventClass.FuncValueDelegateRef +=
                MvvmUtils.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef),
                                                @ref => eventClass.FuncValueDelegateRef -= @ref);

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            int value = 999;
            int i;
            object obj = null;
            
            Assert.AreEqual(eventClass.OnEventFunc(new object()), WeakEventClass.StaticObject);
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), value);

            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), WeakEventClass.Value);
            Assert.AreEqual(value, WeakEventClass.Value);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.AreEqual(obj, WeakEventClass.StaticObject);
            Assert.AreEqual(i, WeakEventClass.Value);

            //clear target
            target.ToString();
            target = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            obj = null;
            Assert.IsNull(eventClass.OnEventFunc(new object()));
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), default(int));

            int value1 = value;
            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), default(int));
            Assert.AreEqual(value, value1);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.IsNull(obj);
            Assert.AreEqual(i, 0);

            Assert.IsNull(eventClass.GetEventFuncDelegates());
            Assert.IsNull(eventClass.GetRefDelagates());
            Assert.IsNull(eventClass.GetFuncValueDelegate());
            Assert.IsNull(eventClass.GetFuncValueDelegateRef());
        }

        [TestMethod]
        public void FindWeakDelegateByOriginalDelegateTest()
        {
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc +=
                MvvmUtils.ConvertToWeakDelegate(new Func<object, object>(target.FuncDelegate));
            eventClass.RefDelegate += MvvmUtils.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate));

            eventClass.FuncValueDelegate +=
                MvvmUtils.ConvertToWeakDelegate(new Func<int, int>(target.FuncValueDelegate));
            eventClass.FuncValueDelegateRef +=
                MvvmUtils.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef));

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Func<object, object> func =
                MvvmUtils.FindWeakDelegateByOriginalDelegate(new Func<object, object>(target.FuncDelegate),
                                                             eventClass.GetEventFuncDelegates());
            RefDelegate refDelegate = MvvmUtils.FindWeakDelegateByOriginalDelegate(new RefDelegate(target.RefDelegate),
                                                                                   eventClass.GetRefDelagates());
            Func<int, int> funcValue =
                MvvmUtils.FindWeakDelegateByOriginalDelegate(new Func<int, int>(target.FuncValueDelegate),
                                                             eventClass.GetFuncValueDelegate());
            FuncValueDelegateRef funcValueRef =
                MvvmUtils.FindWeakDelegateByOriginalDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef),
                                                             eventClass.GetFuncValueDelegateRef());

            Assert.IsNotNull(func);
            Assert.IsNotNull(refDelegate);
            Assert.IsNotNull(funcValue);
            Assert.IsNotNull(funcValueRef);

            eventClass.EventFunc -= func;
            eventClass.RefDelegate -= refDelegate;
            eventClass.FuncValueDelegate -= funcValue;
            eventClass.FuncValueDelegateRef -= funcValueRef;

            Assert.IsNull(eventClass.GetEventFuncDelegates());
            Assert.IsNull(eventClass.GetRefDelagates());
            Assert.IsNull(eventClass.GetFuncValueDelegate());
            Assert.IsNull(eventClass.GetFuncValueDelegateRef());
        }
    }
}